/**
 *  exec_first_free_segment()
 *  najde volny segment pro aplikaci a vrati ho
 */
unsigned int exec_first_free_segment(void) {
  int i;
  for(i=0; i<=16; i++)
    if(_exec_segment_used[i] == false)
      return (i << 12);

  return 0;
}

/**
 *  exec_set_segment_status()
 *  nastavi status <status> segmentu <segment>
 */
void exec_set_segment_status(unsigned int segment, bool status) {
  _exec_segment_used[segment >> 12] = status;
  return;
}

/**
 *  exec_load_app()
 *  nacte aplikaci zacinajici clusterem <cluster> na segment <segment>
 *  a nastavi pametovy break level pro dany segment na <filesize> + 16
 */
void exec_load_app(unsigned int segment, unsigned int cluster, unsigned int filesize) {
  int t = cluster;
  char seg = (segment >> 12);
  char far *app_buffer = (char far *)_exec_far_pointer_table[seg];

  /* rutina pro nahrani souboru do pameti */
  while(1) {
    floppyread((_fat->data_start_sector + (cluster - 2)), 1, app_buffer);
    app_buffer += 0x200;

    cluster = *((unsigned int far *)(_fat->fat+((cluster * 3) / 2)));
    if (t & 1)
      cluster >>= 4;
    else
      cluster &= 0x0fff;
    if (fat_end(cluster))
      break;
    t = cluster;
  }

  /* nastavime aplikaci pametovy break level,
   * tesne nad program ve stejnem segmentu */
  __brklvl[seg]  = filesize + 16;
  mem_alloc_bottom[seg] = NULL;
  return;
}

/**
 *  exec()
 *  najde na disku soubor <filename>, pokud ho najde
 *  zjisti, jestli to je soubor a ne treba slozka,
 *  pokud to je soubor, nahraje ho do pameti na adresu
 *  seg:0000 a provede call na toto misto
 *
 *  navratove hodnoty:
 *  1  - program spusten    [EXEC_OK]
 *  0  - program nenalezen  [EXEC_FAILED]
 */
int exec(char far *filename) {
  unsigned int t;                                 /* zaloha klastru */
  unsigned int cluster;                           /* cluster */
  unsigned int seg;

  char far *far_dirPointer       = EXEC_DIR_PTR;  /* zde se bude zapisovat aktualni slozka, odkud se spoustela aplikace */
  char far *far_argumentsPointer = EXEC_ARG_PTR;  /* zde budou pripravene parametry pro aplikaci */
  char far *far_appName          = EXEC_APPNAME_PTR; /* zde bude nazev spustene aplikace */
  char far *app_buffer           = NULL;          /* pointer na misto, kam se nacte aplikace */
  int i                          = 0;             /* cyklus */
  int arguments_count            = 0;             /* na jake pozici zacina string s parametry */
  DirectoryEntry far *sfile      = mem_alloc(sizeof(DirectoryEntry),
                                 KERNEL_SEGMENT); /* promenna pro hledani souboru */
  char far *filenameLowercase    = mem_alloc(strlen(filename)+1,
                                 KERNEL_SEGMENT); /* pokud je filename malymi pismeny, do teto promenne se prevedou na velka */
  char far *acutalDirBackup      = mem_alloc(strlen(_dir_actual->string)+1,
                                 KERNEL_SEGMENT); /* zaloha aktualni slozky */
  char far *arguments            = mem_alloc(strlen(filename)+1,
                                 KERNEL_SEGMENT); /* parametry pro aplikaci */

  /* zalohujeme si aktualni pracovni slozku */
  strcpy(acutalDirBackup, _dir_actual->string);

  /* vyparsovani parametru pro aplikaci */
  if(pos(filename, ' ') != POS_NOT_FOUND) {       /* |- pokud je v nazvu souboru aspon jedna mezera */
    arguments_count = pos(filename, ' ')+1;       /* do promenne arguments_count se ulozi kde je mezera */
    while(*(filename + arguments_count) != '\0')  /* cyklus zapise do promenne arguments parametry za mezerou */
      *(arguments + i++) = *(filename + arguments_count++);
    *(arguments + i) = '\0';                      /* uzavreme string */
    strcpy(far_argumentsPointer, arguments);      /* pripravime string pro aplikaci */
    filename[arguments_count-i-1] = '\0';         /* ..aby nebyl parametr soucasti nazvu souboru */
    i=0;                                          /* .. <i> se bude jeste pouzivat */
  } else {                                        /* |- aplikace je pustena bez parametru */
    strcpy(arguments, "");
    strcpy(far_argumentsPointer, arguments);
  }

  /* do <filenameLowercase> si zapiseme nazev souboru malymi pismeny */
  strtolower(filename, filenameLowercase);
  filename = filenameLowercase;                   /* prepiseme pointer */

  if(pos(filename, '/') != POS_NOT_FOUND) {       /* pokud je v souboru alespon jedno lomitko */
    filename[l_pos(filename, '/')] = '\0';        /* bude se jednat o cestu, za poslednim lomitkem zavreme string (zbyde nam jen string s nazvem souboru) */
    if(!strlen(filename))
      change_dir("/");
    else
      change_dir(filename);                       /* skocime do slozky */
    filename += strlen(filename)+1;               /* zmenime pointer jen na nazev souboru */
  }

  /* na adresu <far_appName> zapisu nazev souboru */
  strcpy(far_appName, filename);
  /* vyhledame soubor v aktualni slozce */
  while((i = find_file(sfile, i)) != END_OF_DIR) {
    /* soubor nalezen */
    if(!strcmp(sfile->realname, filename)) {
      /* zaznam je soubor */
      if(sfile->attr == F_ARCHIVE) {
        cluster = sfile->ClstrNo;
        t = cluster;
        goto print_out;
      }
    }
  }

  /* zmenime zpet pracovni slozku */
  change_dir(acutalDirBackup);

  /* a uvolnime buffery */
  mem_free(arguments);
  mem_free(acutalDirBackup);
  mem_free(filenameLowercase);
  mem_free(sfile);
  return EXEC_FAILED;

  print_out:
    /* zjistime volny segment, kam bychom mohli aplikaci nahrat */
    seg = exec_first_free_segment();
    exec_set_segment_status(seg, true);
    app_buffer = (char far *)_exec_far_pointer_table[seg >> 12];

    /* zmenime pracovni slozku zpet */
    change_dir(acutalDirBackup);

    /* predame na dannou adresu aktualni slozku, pro nas spousteny soubor */
    strcpy(far_dirPointer, _dir_actual->string);

    /* rutina pro nahrani souboru do pameti */
    exec_load_app(seg, cluster, (unsigned int)sfile->filesize);

    /* dealokujeme buffery */
    mem_free(filenameLowercase);
    mem_free(sfile);

    /* appz ted pouzivaji API pro praci s diskem, pokud tedy nezasahuje
     * aplikace na disk primo (pres int 13h), aplikace tedy nema jadro jak
     * prekvapit, se zmenou FATky...
     */
    change_dir(acutalDirBackup);                  /* zmenime pracovni slozku zpet */
    mem_free(acutalDirBackup);                    /* uvolnime buffer */


    /* samotny proces spousteni v ASM */
    asm cli                           ;

    /* spusteni aplikace... > zalohujeme si stack pointer do registru BX */
    asm mov bx, sp                    ;

    /* nastavime segmentove registry */
    asm mov ax, seg                   ;
    asm mov ds, ax                    ;
    asm mov es, ax                    ;
    asm mov ss, ax                    ;

    /* nastavime stack pointer tesne pred konec segmentu */
    asm mov sp, 0xFFF0                ;

    /* do noveho stacku si ulozime dulezite registry */
    asm push bx                       ;
    asm push di                       ;
    asm push si                       ;
    asm sti                           ;

    /* skok do aplikace */
    exec_call_far(0x2000)             ;

    asm cli                           ;
    /* zde se vratime, vratime zpet dulezite registry */
    asm pop si;
    asm pop di;
    asm pop bx;

    /* nastavime zpet puvodni (kernel) segment [CS by se melo rovnat 0x1000] */
    asm mov ax, cs                    ;
    asm mov ds, ax                    ;
    asm mov es, ax                    ;
    asm mov ss, ax                    ;

    /* a vratime stack pointer */
    asm mov sp, bx                    ;
    asm sti                           ;

    /* segment jiz nepouziva zadna aplikace */
    exec_set_segment_status(seg, false);

  return *EXEC_RETURN_VALUE;
}
